#pragma once
#include "util.hpp"
#include "onlinemanager.hpp"
#include "db.hpp"
#include "log.hpp"
#include "session.hpp"

using namespace std;

#define BOARD_COL 15
#define BOARD_ROW 15
#define WHITE_CHESS 1
#define BLACK_CHESS 2

// 定义一个房间状态
typedef enum
{
	GAME_ON,
	GAME_OVER
} room_statu;
class room
{
public:
	room(uint64_t room_id, online_manager *olmanager, user_table *usrt)
		: _room_id(room_id),
		  _olmanager(olmanager),
		  _usrt(usrt),
		  _board(BOARD_ROW, vector<int>(BOARD_COL, 0)),
		  _statu(GAME_ON)
	{
		DLOG("%lu 房间创建成功!!", _room_id);
	}
	// 处理下棋动作,下棋的时候需要检查下棋的位置是否合理，并且下棋之后是否有一方获胜，下的时候对方玩家是否掉线了
	// 其中，不论本次的下棋结果如何，都需要返回对应当前战局的状态。
	Json::Value handler_chess (Json ::Value input)
	{
		Json::Value resp = input;
		// 2.检查当前的玩家是否还在线
		// 两个用户都要被检查
		if (_olmanager->in_game_room(_white_id) == false)
		{
			// 白子掉线
			resp["result"] = true;
			resp["reason"] = "对方掉线了，不战而胜";
			resp["winner"] = (Json::UInt64)_black_id;
			return resp;
		}
		if (_olmanager->in_game_room(_black_id) == false)
		{
			// 黑子掉线
			resp["result"] = true;
			resp["reason"] = "对方掉线了，不战而胜";
			resp["winner"] = (Json::UInt64)_white_id;
			return resp;
		}
		// 3.检查当前下棋的位置是否合法，比如下出了棋盘外或者当前棋盘已有棋子
		int row = input["row"].asInt();
		int col = input["col"].asInt();
		uint64_t cur_uid = input["uid"].asUInt64();
		if (row > BOARD_ROW || col > BOARD_COL)
		{
			resp["result"] = false;
			resp["reason"] = "落子在棋盘外！";
			return resp;
		}
		if (_board[row][col] != 0)
		{
			resp["result"] = false;
			resp["reason"] = "当前位置已经有了其他棋⼦！";
			return resp;
		}
		// 4.下棋的位置无误，进行结果判定
		int cur_color = cur_uid == _white_id ? WHITE_CHESS : BLACK_CHESS;
		// 更新棋盘
		_board[row][col] = cur_color;

		uint64_t winner_id = check_win(row, col, cur_color);
		if (winner_id != 0)
		{
			// 更新赢家的数据
			resp["reason"] = "五星连珠，胜！";
		}
		resp["result"] = true;
		resp["winner"] = (Json::UInt64)winner_id;
		return resp;
	}

	Json::Value handle_chat(Json::Value &input)
	{
		// 1.过滤掉敏感词
		Json::Value resp = input;
		string message = input["message"].asString();
		if (message.find("菜逼") != message.npos)
		{
			resp["result"] = false;
			resp["reason"] = "不当言论，不能发送";
			return resp;
		}

		resp["result"] = true;
		return resp;
	}
	Json::Value handle_exit(uint64_t uid)
	{
		// 处理玩家退出的情况，两种情况，一种正常退出，另一种游戏途中掉线，特殊处理一种
		DLOG("房间管理器：已发现有用户掉线");
		Json::Value resp;
		if (_statu == GAME_ON)
		{
			uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
			resp["optype"] = "put_chess";
			resp["result"] = true;
			resp["reason"] = "对方掉线，不战而胜！";
			resp["room_id"] = (Json::UInt64)_room_id;
			resp["uid"] = (Json::UInt64)uid;
			resp["row"] = -1;
			resp["col"] = -1;
			resp["winner"] = (Json::UInt64)winner_id;
			uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
			_usrt->win(winner_id);
        	_usrt->lose(loser_id);
            _statu = GAME_OVER;
            boardcast(resp);

		}
		--_player_num;
		return resp;
	}

	void handle_req(Json ::Value req)
	{
		Json ::Value resp;
		// 每一次处理消息前直接检查当前房间号的合法性
		// 1.需要先检查当前的房间号与传入的请求房间号是否匹配
		uint64_t room_id = req["room_id"].asUInt64();
		if (room_id != _room_id)
		{
			resp["optype"] = "put_chess";
			resp["result"] = false;
			resp["reason"] = "房间号不匹配!";
			return boardcast(resp);
		}	
		// 2.根据请求的不同操作要求调用对应的函数
		if (req["optype"].asString() == "put_chess")
		{
			// 落子请求
			resp = handler_chess(req);
			// 若分出胜负，更新数据库
			if (resp["winner"].asUInt64() != 0)
			{
				uint64_t winner_id = resp["winner"].asInt64();
				uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
				_usrt->win(winner_id);
				_usrt->lose(loser_id);
				_statu = GAME_OVER;
			}
		}
		else if (req["optype"].asString() == "chat")
		{
			resp = handle_chat(req);
		}
		else
		{
			resp["optype"] = "put_chess";
			resp["result"] = false;
			resp["reason"] = "未知操作!";
		}
        std::string body;
        Json_util::serialize(resp, body);
        DLOG("房间-广播动作: %s", body.c_str());

		return boardcast(resp);
	}
	void boardcast(Json ::Value &req)
	{
		// 序列化数据
		string body;
		Json_util::serialize(req, body);

		// 获取白棋通信链接
		wsvr_t::connection_ptr wsconp_white = _olmanager->get_conn_from_game_room(_white_id);
		if (wsconp_white.get() != nullptr)
			wsconp_white->send(body);
		else
			DLOG("房间-白棋玩家连接获取失败");


		// 获取黑棋通信链接
		wsvr_t::connection_ptr wsconp_black = _olmanager->get_conn_from_game_room(_black_id);
		if (wsconp_black.get() != nullptr)
			wsconp_black->send(body);
		else
			DLOG("房间-黑棋玩家连接获取失败");

		return;
	}
	~room()
	{
		DLOG("%lu 房间销毁成功!!", _room_id);
	}

	uint64_t id() { return _room_id; }
	room_statu statu() { return _statu; }
	int player_count() { return _player_num; }
	void add_white_user(uint64_t uid)
	{
		_white_id = uid;
		_player_num++;
	}
	void add_black_user(uint64_t uid)
	{
		_black_id = uid;
		_player_num++;
	}
	uint64_t get_white_user() { return _white_id; }
	uint64_t get_black_user() { return _black_id; }

private:
	bool Is_win(int row, int col, int row_fix, int col_fix, int color)
	{
		int find_row = row + row_fix;
		int find_col = col + col_fix;
		int count = 1;

		// 正向查找
		while (find_row < BOARD_ROW && find_col < BOARD_COL &&
			   find_row >= 0 && find_col >= 0 && _board[find_row][find_col] == color)
		{
			++count;
			find_row += row_fix;
			find_col += col_fix;
		}

		// 反向查找前还需要进行一次重置
		find_row = row - row_fix;
		find_col = col - col_fix;


		// 反向查找
		while (find_row < BOARD_ROW && find_col < BOARD_COL &&
			   find_row >= 0 && find_col >= 0 && _board[find_row][find_col] == color)
		{
			++count;
			find_row -= row_fix;
			find_col -= col_fix;
		}

		return count >= 5;
	}
	// 判赢,没有胜负返回0，分出胜负则返回胜者ID
	uint64_t check_win(int row, int col, int color)
	{
		if (Is_win(row, col, 0, 1, color) ||
			Is_win(row, col, 1, 0, color) ||
			Is_win(row, col, -1, 1, color) ||
			Is_win(row, col, -1, -1, color))
		{
			// /任意一个方向上出现了true也就是五星连珠，则设置返回值
			DLOG("当前check win 函数已生效，返回胜利者ID：%d ",color);
            return color == WHITE_CHESS ? _white_id : _black_id;
		}
		else
		{
			DLOG("当前步未能分出胜负！ ",color);
			return 0;
		}
	}

	// 房间内部所需要的各种变量如下：
	// 房间当前内部的玩家数量，白棋和黑棋的ID，棋盘，房间状态，房间的ID，一个用于获取用户数据的sql句柄
	// 用于检查用户是否在线的管理模块
	int _player_num = 0;
	uint64_t _white_id;
	uint64_t _black_id;
	uint64_t _room_id;
	online_manager *_olmanager;
	user_table *_usrt;
	room_statu _statu;
	vector<vector<int>> _board;
};

// 编写一个房间管理对象，这个房间管理对象可以管理当前整个大厅内部的所有房间对象
// 所以，它需要具备的功能有：
// 1.创建房间对象
// 2.销毁房间对象
// 3.通过房间的ID来对房间进行查找
using room_ptr = shared_ptr<room>;
class room_manager
{
public:
	// 构造阶段，初始化我们的成员变量
	room_manager(user_table *usrtm, online_manager *olm)
		: _next_rid(1), _tb_user(usrtm), _online_user(olm)
	{
	}

	// 为两个即将对战的玩家创建房间
	// room(uint64_t white_id, uint64_t black_id, uint64_t room_id, online_manager *olmanager, user_table *usrt)
	// room(uint64_t room_id, online_manager *olmanager, user_table *usrt)

	room_ptr room_create(uint64_t user1, uint64_t user2)
	{
		DLOG("已经凑齐两个玩家，尝试创建房间！");
		// 首先两个玩家都必须在线
		if (_online_user->in_game_hall(user1) == false)
		{
			ELOG("用户%lu 不在大厅中！无法创建房间", user1);
			return room_ptr();
		}
		if (_online_user->in_game_hall(user2) == false)
		{
			ELOG("用户%lu 不在大厅中！无法创建房间", user2);
			return room_ptr();
		}

		unique_lock<mutex> lock(_mutex);
		// 创建房间对象
		DLOG("创建房间对象");

		room_ptr roomp(new room(_next_rid, _online_user, _tb_user));
		roomp->add_white_user(user1);
		roomp->add_black_user(user2);

		// 将房间对象放入管理容器中
		_rooms.insert(make_pair(_next_rid, roomp));

		// 将用户ID同房间ID做一个映射
		_users.insert(make_pair(user1, _next_rid));
		_users.insert(make_pair(user2, _next_rid));
		_next_rid++;

		// 返回房间信息
		return roomp;
	}

	// 通过房间ID返回房间信息
	room_ptr get_room_by_RoomID(uint64_t roomid)
	{
		unique_lock<mutex> lock(_mutex);
		if (_rooms.find(roomid) == _rooms.end())
		{
			ELOG("房间%lu 不存在！无法查询", roomid);
			return room_ptr();
		}
		else
			return _rooms[roomid];
	}

	// 通过用户ID返回房间信息
	room_ptr get_room_by_UserID(uint64_t userid)
	{
		unique_lock<mutex> lock(_mutex);

		// 1. 通过⽤⼾ID获取房间ID
		auto uit = _users.find(userid);
		if (uit == _users.end())
		{
			return room_ptr();
		}
		uint64_t rid = uit->second;
		// 2. 通过房间ID获取房间信息
		auto rit = _rooms.find(rid);
		if (rit == _rooms.end())
		{
			return room_ptr();
		}
		return rit->second;
	}

	// 通过房间ID销毁对应房间
	void remove_room(uint64_t roomid)
	{
		// 不仅需要移除房间ID，还要移除用户ID
		room_ptr rp = get_room_by_RoomID(roomid);
		if (rp.get() == nullptr)
			return;

		uint64_t white_id = rp->get_white_user();
		uint64_t black_id = rp->get_black_user();
		unique_lock<mutex> lock(_mutex);

		_users.erase(white_id);
		_users.erase(black_id);

		_rooms.erase(roomid);
	}

	// 删除房间中的指定用户
		void remove_room_user(uint64_t uid)
		{
			room_ptr rp = get_room_by_UserID(uid);
			if (rp.get() == nullptr)
			{
				return;
			}
			// 处理房间中玩家退出动作
			rp->handle_exit(uid);
			// 房间中没有玩家了，则销毁房间
			if (rp->player_count() == 0)
			{
				remove_room(rp->id());
			}
			return;
		}

	~room_manager()
	{
	}

private:
	// 管理房间对象，我们使用一个智能指针和房间id的键值对来管理
	// 要有房间的ID，管理键值对的容器,一个互斥锁，一个房间计数器，在线管理对象，用户数据管理对象
	uint64_t _next_rid;
	mutex _mutex;
	user_table *_tb_user;
	online_manager *_online_user;
	unordered_map<uint64_t, room_ptr> _rooms;
	unordered_map<uint64_t, uint64_t> _users;
};